﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using System.IO;
using Newtonsoft.Json.Linq;

namespace BoneImp {
	public class SkeletonJson {
		AttachmentLoader attachmentLoader;
		string error;

		//Was internal class
		private bool ownsLoader;

		public SkeletonJson(string Path) {
			this.createWithLoader(new TextureAttachmentLoader(Path));
		}

		public SkeletonJson(AttachmentLoader attachmentLoader){
			this.createWithLoader(attachmentLoader);
		}

		public SkeletonJson(Atlas atlas) {
			AtlasAttachmentLoader attachmentLoader = new AtlasAttachmentLoader(atlas);
			this.createWithLoader(attachmentLoader);
			this.ownsLoader = true;
		}

		void createWithLoader(AttachmentLoader attachmentLoader) {
			this.attachmentLoader = attachmentLoader;
		}

		public SkeletonData readSkeletonDataFile(string path) {
			JObject file = JObject.Parse(new StreamReader(path).ReadToEnd());
			SkeletonData data = new SkeletonData();

			ParseBones(data, (JArray)file["bones"]);
			ParseSlots(data, (JArray)file["slots"]);
			ParseSkins(data, (JObject)file["skins"]);
			ParseAnimations(data, (JObject)file["animations"]);
			return data;
		}	

		private void ParseBones(SkeletonData data, JArray bones) {
			foreach (JObject bone in bones) {
				BoneData bd = new BoneData((string)bone["name"]);

				string parent = (string)bone["parent"];
				if (parent != null) {
					bd.parent = data.findBone(parent);
				}

				TryGetValue(bone, "x", ref bd.x);
				TryGetValue(bone, "y", ref bd.y);
				TryGetValue(bone, "length", ref bd.length);
				TryGetValue(bone, "rotation", ref bd.rotation);

				data.bones.Add(bd);
			}
		}

		private void ParseSlots(SkeletonData data, JArray slots) {
			foreach (JObject slot in slots) {
				BoneData bd = data.findBone((string)slot["bone"]);
				SlotData sd = new SlotData((string)slot["name"], bd);

				TryGetValue(slot, "attachment", ref sd.attachmentName);

				data.slots.Add(sd);
			}
		}

		private void ParseSkins(SkeletonData data, JObject skins) {
			foreach (KeyValuePair<string, JToken> val in skins) {
				Skin skin = new Skin(val.Key);

				//Go through each entry
				foreach (KeyValuePair<string, JToken> slot in (JObject)val.Value) {
					int SlotIndex = data.findSlotIndex(slot.Key);

					foreach (KeyValuePair<string, JToken> attachment in (JObject)slot.Value){
						string AttachmentName = attachment.Key;

						string RegionName = (string)attachment.Value["name"];
						if (RegionName == null) {
							RegionName = AttachmentName;
						}

						RegionAttachment ra = new RegionAttachment(AttachmentName, attachmentLoader.findRegion(RegionName));
						JObject ad = (JObject)attachment.Value;
						
						TryGetValue(ad, "x", ref ra.x);
						TryGetValue(ad, "y", ref ra.y);
						TryGetValue(ad, "width", ref ra.width);
						TryGetValue(ad, "height", ref ra.height);
						TryGetValue(ad, "rotation", ref ra.rotation);
						TryGetValue(ad, "scaleX", ref ra.scaleX);
						TryGetValue(ad, "scaleY", ref ra.scaleY);
						ra.updateOffset();

						skin.addAttachment(SlotIndex, AttachmentName, ra);
					}
				}

				data.skins.Add(skin);
				if (skin.name == "default") {
					data.defaultSkin = skin;
				}
			}
		}

		private void ApplyCurvedKeyframe(CurveTimeline ctl, int index, JObject keyframe) {
			if (keyframe.Type == JTokenType.String) {
				string type = (string)keyframe["curve"];
				if (type == "stepped") {
					ctl.setStepped(index);
				} else if (type == "linear") {
					ctl.setLinear(index);
				}
			} else if (keyframe.Type == JTokenType.Array) {
				JArray CurveData = (JArray)keyframe["curve"];
				ctl.setCurve(index, (float)CurveData[0], (float)CurveData[1], (float)CurveData[2], (float)CurveData[3]);
			}
		}

		private void ParseAnimations(SkeletonData data, JObject animations) {
			foreach (KeyValuePair<string, JToken> val in animations) {
				Animation animation = new Animation(val.Key);

				//Bones
				foreach (KeyValuePair<string, JToken> bone in (JObject)val.Value["bones"]) {
					int BoneIndex = data.findBoneIndex(bone.Key);

					if (bone.Value["rotate"] != null){
						JArray keyframes = (JArray)bone.Value["rotate"];

						RotateTimeline timeline = new RotateTimeline(keyframes.Count);
						for (int i = 0; i < keyframes.Count; i++){
							JObject keyframe = (JObject)keyframes[i];
							timeline.setFrame(i, (float)keyframe["time"], (float)keyframe["angle"]);

							ApplyCurvedKeyframe(timeline, i, keyframe);
						}

						timeline.boneIndex = BoneIndex;
						animation.timelines.Add(timeline);
					}

					if (bone.Value["translate"] != null) {
						JArray keyframes = (JArray)bone.Value["translate"];

						TranslateTimeline timeline = new TranslateTimeline(keyframes.Count);
						for (int i = 0; i < keyframes.Count; i++) {
							JObject keyframe = (JObject)keyframes[i];
							timeline.setFrame(i, (float)keyframe["time"], (float)keyframe["x"], (float)keyframe["y"]);

							ApplyCurvedKeyframe(timeline, i, keyframe);
						}

						timeline.boneIndex = BoneIndex;
						animation.timelines.Add(timeline);
					}

					if (bone.Value["scale"] != null) {
						JArray keyframes = (JArray)bone.Value["scale"];

						ScaleTimeline timeline = new ScaleTimeline(keyframes.Count);
						for (int i = 0; i < keyframes.Count; i++) {
							JObject keyframe = (JObject)keyframes[i];
							timeline.setFrame(i, (float)keyframe["time"], (float)keyframe["x"], (float)keyframe["y"]);

							ApplyCurvedKeyframe(timeline, i, keyframe);
						}

						timeline.boneIndex = BoneIndex;
						animation.timelines.Add(timeline);
					}
					
				}

				//Slots

				data.animations.Add(animation);
			}
		}

		#region Utility Functions
		private void TryGetValue(JObject from, string name, ref string dest) {
			if (from[name] != null) {
				dest = (string)from[name];
			}
		}

		private void TryGetValue(JObject from, string name, ref int dest) {
			if (from[name] != null) {
				dest = (int)from[name];
			}
		}

		private void TryGetValue(JObject from, string name, ref float dest) {
			if (from[name] != null) {
				dest = (float)from[name];
			}
		}
		#endregion
	}
}
